/* MSNP15 SSO
 * Based on the Linux C implementation. 
 * Compile: 'gcc -o sso sso.c -lssl -g -Wall'
 *
 * Licensed for distribution under the GPL version 2
 * Copyright 2007 Youngway <admin@eroster.cn>
 * http://www.eroster.cn
 * date: 2007-09-30
 */

#include "common.h"
#include "msn_sso.h"
#include "msn_soap2.h"

static msn_sso_t *msn_sso_init(msn_session_t *ms);
static void msn_sso_clean(msn_sso_t *sso);
static void msn_sso_connect_finish(int fd, int error, int res, void *data);
static int msn_sso_response_parse(const char *buf, const int size, msn_session_t *robot);
static void msn_sso_usr_send(msn_session_t *robot);
static void msn_sso_usr_send_finish(int fd, int error, int res, void *data);

static const char *codes ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const unsigned char map[256] = {
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255,  62, 255, 255, 255,  63,
 52,  53,  54,  55,  56,  57,  58,  59,  60,  61, 255, 255,
255, 254, 255, 255, 255,   0,   1,   2,   3,   4,   5,   6,
  7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,
 19,  20,  21,  22,  23,  24,  25, 255, 255, 255, 255, 255,
255,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,  36,
 37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,  48,
 49,  50,  51, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255 };

int base64_encode(const unsigned char *in,  unsigned long inlen, \
				unsigned char *out, unsigned long *outlen);
int base64_decode(const unsigned char *in,  unsigned long inlen, \
				unsigned char *out, unsigned long *outlen);

int base64_encode(const unsigned char *in,  unsigned long inlen, \
				unsigned char *out, unsigned long *outlen)
{
	unsigned long i, len2, leven;
   	unsigned char *p;

   	if(in==NULL || out==NULL || outlen==NULL){
		debug_log(5, "%s\n", "some parameters are null");
  		return -1;
 	}
   	/* valid output size ? */
   	len2 = 4 * ((inlen + 2) / 3);
   	if (*outlen < len2 + 1) {
      	*outlen = len2+1;
   	}
   	p = out;
   	leven = 3*(inlen / 3);
   	for (i = 0; i < leven; i += 3) {
       *p++ = codes[(in[0] >> 2) & 0x3F];
       *p++ = codes[(((in[0] & 3) << 4) + (in[1] >> 4)) & 0x3F];
       *p++ = codes[(((in[1] & 0xf) << 2) + (in[2] >> 6)) & 0x3F];
       *p++ = codes[in[2] & 0x3F];
       in += 3;
   }
   /* Pad it if necessary...  */
   if (i < inlen) {
       unsigned a = in[0];
       unsigned b = (i+1 < inlen) ? in[1] : 0;

       *p++ = codes[(a >> 2) & 0x3F];
       *p++ = codes[(((a & 3) << 4) + (b >> 4)) & 0x3F];
       *p++ = (i+1 < inlen) ? codes[(((b & 0xf) << 2)) & 0x3F] : '=';
       *p++ = '=';
   }

   /* append a NULL byte */
   *p = '\0';

   /* return ok */
   *outlen = p - out;
   return 0;
}


int base64_decode(const unsigned char *in,  unsigned long inlen, \
				unsigned char *out, unsigned long *outlen)
{
   	unsigned long t, x, y, z;
   	unsigned char c;
   	int           g;

   	if(in==NULL || out==NULL || outlen==NULL){
		debug_log(5, "%s\n", "some parameters are null");
  		return -1;
 	}

   	g = 3;
   	for (x = y = z = t = 0; x < inlen; x++) {
       c = map[in[x]&0xFF];
       if (c == 255) continue;
       /* the final = symbols are read and used to trim the remaining bytes */
       if (c == 254) {
          c = 0;
          /* prevent g < 0 which would potentially allow an overflow later */
          if (--g < 0) {
             return 0;
          }
       } else if (g != 3) {
          /* we only allow = to be at the end */
          return 0;
       }

       t = (t<<6)|c;

       if (++y == 4) {
          if (z + g > *outlen) {
             return 0;
          }
          out[z++] = (unsigned char)((t>>16)&255);
          if (g > 1) out[z++] = (unsigned char)((t>>8)&255);
          if (g > 2) out[z++] = (unsigned char)(t&255);
          y = t = 0;
       }
   }
   if (y != 0) {
       return -1;
   }
   *outlen = z;
   return 0;
}


unsigned char * mt_sso_hmac_sha1(unsigned char *key, int keylen, unsigned char *subject, \
				int slen, int *mac_len)
{
	unsigned char *macbuf;

	macbuf = (unsigned char *)g_malloc0(EVP_MAX_MD_SIZE);
	if(macbuf==NULL){
    	error_log(errno, "memory is empty\n");
        return NULL;
	}
	HMAC(EVP_sha1(), key, keylen, subject, slen, macbuf, mac_len);
    debug_log(5, "maclen %d\n",*mac_len);
	return macbuf;
}

unsigned char * mt_sso_derive_key(unsigned char *key, int keylen, unsigned char *magic, \
				int magiclen, int *outlen)
{
  	int len1, len2,len3,len4;
	unsigned char *hash1 = mt_sso_hmac_sha1(key, keylen, magic, strlen(magic),&len1);   
    int hashlen = len1+magiclen;
    unsigned char *hash = (unsigned char *)g_malloc0(len1+magiclen+1);
       
   	memset(hash, 0x0, hashlen+1);
	memcpy(hash,hash1, len1);
	memcpy(hash+len1, magic, magiclen);
	unsigned char *hash2 = mt_sso_hmac_sha1(key, keylen, hash, hashlen, &len2);
    g_free(hash);
        
	unsigned char *hash3 = mt_sso_hmac_sha1(key, keylen, hash1, len1, &len3);

  	hashlen = len3+magiclen;
	hash = (unsigned char *)malloc(hashlen+1);
	memset(hash, 0x0, hashlen+1);
	memcpy(hash,hash3, len3);
	memcpy(hash+len3, magic, magiclen);
        
	unsigned char *hash4 = mt_sso_hmac_sha1(key, keylen, hash, hashlen, &len4);
	g_free(hash);
        
	*outlen = len2+4;
	unsigned char *out = (unsigned char *)malloc(*outlen);
	memset(out, 0x0, *outlen);
	memcpy(out, hash2, len2);
	memcpy(out+len2, hash4, 4);
	g_free(hash1);
	g_free(hash2);
    g_free(hash3);
	g_free(hash4);
        
	return out;
}

unsigned char * mt_sso_ciphertext(char *nonce, int noncelen, char *secret, int secretlen)
{
	int i=0;
	unsigned char iv[8]={0x46, 0xC4, 0x14, 0x9F, 0xFF, 0xFC, 0x91 ,0x61};                    
	unsigned char *key1= g_malloc0(24);
	unsigned long key1len=29;
        
	i = base64_decode(secret, secretlen, key1, &key1len);
	debug_log(5, "decode return %d\n",i);
        
	debug_log(5, "key1len: %ld\n", key1len);
	int key2len, key3len, hashlen;
	char magic1[] = "WS-SecureConversationSESSION KEY HASH";
	char magic2[]="WS-SecureConversationSESSION KEY ENCRYPTION";
	unsigned char *key2 = mt_sso_derive_key(key1, key1len, magic1, strlen(magic1), &key2len);
	unsigned char *key3 = mt_sso_derive_key(key1, key1len, magic2, strlen(magic2), &key3len);
  	/*
  	printf("key2len: %d, key3len %d\n", key2len, key3len);
  	printf("key2:");
	for(i=0;i<key2len;i++)
		printf("%02x ", key2[i]);
	printf("\n");
	*/
  
    unsigned char *hash = mt_sso_hmac_sha1(key2, key2len, nonce, noncelen, &hashlen);
	unsigned char *nonce1 = (unsigned char *)g_malloc0(noncelen+8);
    
	memset(nonce1, 0x0, noncelen+8);
	strcpy(nonce1, nonce);
    int j=0;    
    for(i=0;i<8;i++){
		nonce1[noncelen+i]=0x08;
	}
  	//printf("key3:");
  	//for(i=0;i<key3len;i++)
   		//printf("%x ",key3[i]);
  	//printf("\n");
  	unsigned char cbc_key[8];
  	unsigned char cbc2_key[8];
  	unsigned char cbc3_key[8]; 
  	unsigned char *result = g_malloc0(500);
    
	memset(result,0x0,500);
        
  	for(i=0;i<8;i++){
   		cbc_key[i]=key3[i]; 
  	}
  
	for(i=0;i<8;i++){
   		cbc2_key[i]=key3[8+i]; 
  	}
  
  	for(i=0;i<8;i++){
   		cbc3_key[i]=key3[16+i];
  	}
  
  	des_key_schedule ks,ks2,ks3;
  
  	if ((j=DES_set_key(&cbc_key,&ks)) != 0) {
   		debug_log(5, "Key error %d\n",j);
  	}
  
  	if ((j=DES_set_key(&cbc2_key,&ks2)) != 0) {
   		debug_log(5, "Key error %d\n",j);
  	}
  
  	if ((j=DES_set_key(&cbc3_key,&ks3)) != 0) {
   		debug_log(5, "Key error %d\n",j);
  	}
          
	des_cblock iv3;
	memcpy(iv3,iv,sizeof(iv));
	int reslen=noncelen+8;
        
	des_ede3_cbc_encrypt(nonce1,result,reslen,ks,ks2,ks3,&iv3, DES_ENCRYPT);   
	unsigned char start[28];
	memset(start, 0x0, 28);
	//uStructHeaderSize = 28
        start[0] = 0x1c;
        start[1] = 0x00;
        start[2] = 0x00;
        start[3] = 0x00;

        //uCryptMode = 1
        start[4] = 0x01;
        start[5] = 0x00;
        start[6] = 0x00;
        start[7] = 0x00;

        //uCipherType = 0x6603
        start[8] = 0x03;
        start[9] = 0x66;
        start[10] = 0x00;
        start[11] = 0x00;

        //uHashType = 0x8004
        start[12] = 0x04;
        start[13] = 0x80;
        start[14] = 0x00;
        start[15] = 0x00;

        //uIVLen = 8
        start[16] = 0x08;
        start[17] = 0x00;
        start[18] = 0x00;
        start[19] = 0x00;

        //uHashLen = 20
        start[20] = 0x14;
        start[21] = 0x00;
        start[22] = 0x00;
        start[23] = 0x00;

        //uCipherLen = 72
        start[24] = 0x48;
        start[25] = 0x00;
        start[26] = 0x00;
        start[27] = 0x00;
        
        unsigned char text[129];
        memset(text,0x0,129);
        
        for(i=0;i<28;i++)
         text[i]=start[i];
        
        for(i=0;i<8;i++)
         text[28+i] = iv[i];
        
        
        for(i=0;i<hashlen;i++)
         text[36+i] = hash[i];
        
        for(i=0;i<reslen;i++)
         text[36+hashlen+i] = result[i];
        
        unsigned char *out = malloc(180);
        memset(out,0x0,180);
        unsigned long outlen=100;
        unsigned long textlen = 36+hashlen+reslen;
        i =base64_encode(text,  textlen, out, &outlen);
        
		debug_log(5, "encode return %d\n", i);
        debug_log(5, "outlen %ld, textlen %ld\n", outlen, textlen);
        debug_log(5, "out %s\n", out);
        g_free(key1);
        g_free(key2);
        g_free(key3);
        g_free(hash);
        g_free(nonce1);
        
	return out;
}

void msn_sso_start(msn_session_t *ms)
{	
	msn_sso_t *sso;
	struct sockaddr_in srv;

	sso = msn_sso_init(ms);
	srv.sin_port = htons(443);
	srv.sin_family = AF_INET;
	srv.sin_addr = sso->addr;
	
	sock_connect(sso->ev, sso->fd, &srv, sso, msn_sso_connect_finish);
}

static void msn_sso_connect_finish(int fd, int error, int res, void *data)
{
	msn_sso_t *sso = data;
	msn_session_t *ms= NULL;
	GString *soap2 = NULL;
	int size = 0;
	int in = 0;
	int header = 0;
	int body = 0;
	int offset = 0;
	int space = MAX_BUFFER;
	char *readbuf;
	FILE *out = NULL;
	const char *out_path = "/tmp/sso_response.xml";

	assert(sso);
	if(error) {
		debug_log(0, "%s", "sso connect failed\n");
		return;
	}
	debug_log(9, "fd %d connected\n", fd);
	sock_blocking_set(sso->fd);
	ms = sso->ms;
	g_assert(ms);
	if(-1 == sock_ssl_connect(ms->ssl, sso->fd)) {
		debug_log(0, "%s", "sso ssl connect failed\n");
		return ;
	}
	soap2 = msn_sso_request_soap_gen(ms->name->str, ms->pass->str);
	if((size = SSL_write(ms->ssl, soap2->str, soap2->len)) <= 0) {
		print_errors();	
		return;
	}
	readbuf = g_malloc0(space);
	unlink(out_path);
	if((out = fopen(out_path, "w")) == NULL) {
		error_log(errno, "failed to open %s\n", out_path);
	}
	while((size = SSL_read(ms->ssl, readbuf + offset, space - offset)) > 0) {
		if(in == 0) {
			header = headers_end(readbuf, size);
			debug_log(9, "header size %d\n", header);
			char *cl = strcasestr(readbuf, "Content-Length");
			cl = strchr(cl, ':');
			if(cl) {
				cl++;
				while(isspace(*cl)) cl++;
				body = atoi(cl);
				debug_log(9, "content size %d\n", body);
				readbuf = g_realloc(readbuf, header + body);
				space = header + body;
			}
		}
		offset += size;
		in += size;
		if(in >= header + body) 
			break;
	}
	debug_log(9, "{%s} received response finished\n", readbuf);
	int ret = fwrite(readbuf + header, space - header, 1, out);
	if(ret == -1 ) {
		error_log(errno, "failed to write %s\n", out_path);
	}
	if(-1 == msn_sso_response_parse(readbuf + header, space - header, ms)) {
		debug_log(0, "%s\n", "failed to parse %s", out_path);
		fclose(out);
		g_free(readbuf);
		msn_sso_clean(sso);
		return;
	}
	fclose(out);
	g_free(readbuf);
	msn_sso_clean(sso);
	msn_sso_usr_send(ms);
}

static msn_sso_t *msn_sso_init(msn_session_t *ms)
{
	msn_sso_t *sso = g_slice_new(msn_sso_t);
	
	sso->fd = sock_open_from_host("login.live.com", &sso->addr);
	assert(sso->fd > 0);
	sso->ev = xevent_new();
	xevent_set(sso->ev, sso->fd, SOCK_FLAG_WRITE, NULL, NULL);
	xevent_base_set(sso->ev, master_base);
	sso->ms = ms;

	return sso;
}

static void msn_sso_clean(msn_sso_t *sso)
{
	if(!sso)
		return;
	sock_close(sso->fd);
	xevent_clean(sso->ev);
	g_slice_free(msn_sso_t, sso);
}

static int msn_sso_response_parse(const char *buf, const int size, msn_session_t *ms)
{
	int i = 0;
	xmlDocPtr doc;
	xmlXPathContextPtr context;
	xmlXPathObjectPtr result;

	doc = xmlReadMemory(buf, size, NULL, "utf-8", 0);
	if(doc == NULL) {
		debug_log(0, "%s", "failed to parse %s\n", buf);
		goto error;
	}
	context = xmlXPathNewContext(doc);
	if (context == NULL) {   
		debug_log(0, "%s", "failed to create context\n");
		goto error;
	}
	xmlXPathRegisterNs(context, "wst", "http://schemas.xmlsoap.org/ws/2004/04/trust");
	xmlXPathRegisterNs(context, "S", "http://schemas.xmlsoap.org/soap/envelope/");
	result = xmlXPathEvalExpression(\
			BAD_CAST "/S:Envelope/S:Body/wst:RequestSecurityTokenResponseCollection/wst:RequestSecurityTokenResponse",\
		   	context);
	if(result == NULL) {
		debug_log(0, "%s", "failed to search wst:RequestSecurityTokenResponse\n");
		goto error;
	}
	if (xmlXPathNodeSetIsEmpty(result->nodesetval)) {
		debug_log(0, "%s", "failed to search wst:RequestSecurityTokenResponse\n");
		goto error;
	}
	xmlNodeSetPtr nodes = result->nodesetval;
	char *ticket; 
	char *binary;
	for(i = 0; i < nodes->nodeNr; i++) {
		xmlNodePtr node = nodes->nodeTab[i];
		xmlNodePtr children;
		security_token_t *token = security_token_init();

		assert(token);
		for(children = node->children; children; children = children->next) {
			debug_log(9, "[%d] %s\n", i, children->name);
			if(!strcasecmp(children->name, "AppliesTo")) {
				xmlNodePtr children1 = children->children;
				debug_log(9, "[->%d] %s\n", i, children1->name);
				xmlNodePtr address = children1->children;
				if(address) {
					xmlChar *domain = xmlNodeGetContent(address);
					token->domain = g_string_new(domain);
					debug_log(9, "[->->%d] %s %s\n", i, address->name, token->domain->str);
				}
			}
			else if(!strcasecmp(children->name, "RequestedSecurityToken")) {
				xmlNodePtr BinarySecurityToken = children->children;
				ticket = strdup(xmlNodeGetContent(BinarySecurityToken));
				token->ticket = g_string_new(ticket);
				debug_log(9, "[%d] ticket %s\n", i, token->ticket->str);
			}
			else if(!strcasecmp(children->name, "RequestedProofToken")) {
				xmlNodePtr RequestedProofToken = children->children;
				binary = strdup(xmlNodeGetContent(RequestedProofToken));
				token->binary = g_string_new_len(binary, strlen(binary));
				debug_log(9, "[%d] binary secret %s\n", i, binary);
			}
			else if(!strcasecmp(children->name, "PolicyReference")) {
				xmlChar *uri = xmlGetProp(children, BAD_CAST "URI");
				token->uri = g_string_new_len(uri, strlen(uri));
				debug_log(9, "[%d] URI %s\n", i, uri);
			}
			else 
				(void *)0;
		}
		/* append to tokens */
		if((ms->sec_tokens = g_list_append(ms->sec_tokens, token)) == NULL) 
			debug_log(0, "%s", "failed to append security token\n");
	}
	xmlXPathFreeObject(result);
	xmlXPathFreeContext(context);
	xmlFreeDoc(doc);
	return 0;
error:
	xmlXPathFreeObject(result);
	xmlXPathFreeContext(context);
	xmlFreeDoc(doc);
	return -1;
}

static void msn_sso_usr_send(msn_session_t *ms)
{
	GString *auth = NULL;
	char *ciphertext = NULL;
	security_token_t *token = NULL;
	
	if(!ms)
		return;
	assert(ms->nonce);
	token = security_token_find_by_domain(ms->sec_tokens, "messengerclear.live.com");
	if(!token) 
		return;
	auth = g_string_new(NULL);
	ciphertext = mt_sso_ciphertext(ms->nonce->str, ms->nonce->len, \
					token->binary->str, token->binary->len);
	assert(ciphertext);
	g_string_printf(auth, "USR %d SSO S %s %s\r\n", ms->tr_id++, token->ticket->str, ciphertext);
	debug_log(9, "Client Auth {%s}\n", auth->str);
	sock_write(ms->ns->ev, ms->ns->fd, msn_sso_usr_send_finish, ms, auth);
	g_string_free(auth, 1);
}

static void msn_sso_usr_send_finish(int fd, int error, int res, void *data)
{
	msn_session_t *robot = data;
	
	if(error) {
		error_log(errno, "failed to send auth info\n");
		return;
	}
	/* begin to read server response */
	xevent_update(robot->ns->ev, SOCK_FLAG_READ, robot, msn_read_from_ns);
}

#define BUFSIZE 256

static int isBigEndian(void)
{
  short int word = 0x0100;
  char *byte = (char *)&word;
  return(byte[0]);
}

static unsigned int swapInt(unsigned int dw)
{
  unsigned int tmp;
  tmp =  (dw & 0x000000FF);
  tmp = ((dw & 0x0000FF00) >> 0x08) | (tmp << 0x08);
  tmp = ((dw & 0x00FF0000) >> 0x10) | (tmp << 0x08);
  tmp = ((dw & 0xFF000000) >> 0x18) | (tmp << 0x08);
  return(tmp);
}

void msn_chl_challenge(char *input, char *output)
{
  char *productKey = PRODUCT_KEY,
       *productID  = PRODUCT_ID,
       *hexChars   = "0123456789abcdef",
       buf[BUFSIZE];
  unsigned char md5Hash[16], *newHash;
  unsigned int *md5Parts, *chlStringParts, newHashParts[5];
  
  long long nHigh=0, nLow=0;
  
  int i, bigEndian;

  /* Determine our endianess */
  bigEndian = isBigEndian();

  /* Create the MD5 hash */
  snprintf(buf, BUFSIZE-1, "%s%s", input, productKey);
  MD5((unsigned char *)buf, strlen(buf), md5Hash);

  /* Split it into four integers */
  md5Parts = (unsigned int *)md5Hash;
  for(i=0; i<4; i++)
  {  
    /* check for endianess */
    if(bigEndian)
      md5Parts[i] = swapInt(md5Parts[i]);
    
    /* & each integer with 0x7FFFFFFF          */
    /* and save one unmodified array for later */
    newHashParts[i] = md5Parts[i];
    md5Parts[i] &= 0x7FFFFFFF;
  }
  
  /* make a new string and pad with '0' */
  snprintf(buf, BUFSIZE-5, "%s%s", input, productID);
  i = strlen(buf);
  memset(&buf[i], '0', 8 - (i % 8));
  buf[i + (8 - (i % 8))]='\0';
  
  /* split into integers */
  chlStringParts = (unsigned int *)buf;
  
  /* this is magic */
  for (i=0; i<(strlen(buf)/4)-1; i+=2)
  {
    long long temp;

    if(bigEndian)
    {
      chlStringParts[i]   = swapInt(chlStringParts[i]);
      chlStringParts[i+1] = swapInt(chlStringParts[i+1]);
    }

    temp=(md5Parts[0] * (((0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF)+nHigh) + md5Parts[1])%0x7FFFFFFF;
    nHigh=(md5Parts[2] * (((long long)chlStringParts[i+1]+temp) % 0x7FFFFFFF) + md5Parts[3]) % 0x7FFFFFFF;
    nLow=nLow + nHigh + temp;
  }
  nHigh=(nHigh+md5Parts[1]) % 0x7FFFFFFF;
  nLow=(nLow+md5Parts[3]) % 0x7FFFFFFF;
  
  newHashParts[0]^=nHigh;
  newHashParts[1]^=nLow;
  newHashParts[2]^=nHigh;
  newHashParts[3]^=nLow;
  
  /* swap more bytes if big endian */
  for(i=0; i<4 && bigEndian; i++)
    newHashParts[i] = swapInt(newHashParts[i]); 
  
  /* make a string of the parts */
  newHash = (unsigned char *)newHashParts;
  
  /* convert to hexadecimal */
  for (i=0; i<16; i++)
  {
    output[i*2]=hexChars[(newHash[i]>>4)&0xF];
    output[(i*2)+1]=hexChars[newHash[i]&0xF];
  }
  
  output[32]='\0';
}

#if 0
int main()
{
  char chl_result[32];
  
  MSN_handle_chl("30274743417463305461", chl_result);
  
  /* chl_result should be '40575bf9740af7cad8671211e417d0cb' */
  printf("Results: %s\n", chl_result);
  return 0;
}
#endif
